#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <glib.h>
#include <fnmatch.h>
#include "Regex.h"
#include "Vector.h"
#include "my_strdup.h"

/* Copy extracted subexpressions into a vector. */
Vector* match2vector(const char* input, regmatch_t* matches, int length);
Vector* match2vector(const char* input, regmatch_t* matches, int length)
{
  Vector* vector = vector_new(length);
  char*   match  = NULL;
  int     i      = 0;
  int     len    = 0;

  for (i = 1; i <= length; i++, ++matches) {
    len   = matches->rm_eo - matches->rm_so;
    match = strndup(input + matches->rm_so, len);
    vector_set(vector, i - 1, match);
  }

  return vector;
}


Regex* regex_new(const char* pattern, int flags)
{
  Regex* regex   = (Regex*)calloc(1, sizeof(Regex));
  regex->pattern = (char*)calloc(strlen(pattern) + 2, sizeof(char));
  *(regex->pattern) = '^';
  strcpy(regex->pattern + 1, pattern);
  if (flags == 0)
    regex->flags = REG_EXTENDED; /* | REG_NEWLINE | REG_ICASE; */
  else
    regex->flags = flags;
  return regex;
}


void regex_free(Regex* regex)
{
  if (regex->compiled1 != NULL) {
    regfree(regex->compiled1);
    free(regex->compiled1);
  }
  if (regex->compiled2 != NULL) {
    regfree(regex->compiled2);
    free(regex->compiled2);
  }
  free(regex->pattern);
  free(regex);
}


static void regex_compile1(Regex* regex)
{
  /* Compile the regular expression. */
  regex->compiled1 = (regex_t*)malloc(sizeof(regex_t));
  //printf("Compiling '%s'\n", regex->pattern);
  if (regcomp(regex->compiled1,
      regex->pattern,
      regex->flags) != 0) {
    printf("Error: Invalid regular expression '%s'", regex->pattern);
    exit(1);
  }
}


static void regex_compile2(Regex* regex)
{
  /* Compile the regular expression. */
  regex->compiled2 = (regex_t*)malloc(sizeof(regex_t));
  if (regcomp(regex->compiled2,
      regex->pattern + 1,
      regex->flags) != 0) {
    printf("Error: Invalid regular expression '%s'", regex->pattern);
    exit(1);
  }
}


short int regex_matches(Regex* regex, const char* string)
{
  //printf("Matching %s against %s\n", regex->pattern, string);
  if (regex->compiled2 == NULL)
    regex_compile2(regex);

  /* Match against the regular expression. */
  if (regexec(regex->compiled2, string, 0, NULL, 0) == 0) /* REG_NOTBOL | REG_NOTEOL) != 0) */
    return 1;
  
  return 0;
}


Vector* regex_match(Regex* regex, const char* string)
{
  Vector* result = NULL;

  //printf("Matching '%s' against '%s'\n", regex->pattern, string);
  if (regex->compiled1 == NULL)
    regex_compile1(regex);
  
  {
    int        n_matches = regex->compiled1->re_nsub + 1;
    regmatch_t matches[n_matches];

    /* Match against the regular expression. */
    if (regexec(regex->compiled1, string, n_matches, matches, 0) != 0)
      return NULL;

    /* Copy extracted subexpressions into a vector. */
    result = match2vector(string, matches, n_matches);
  }

  return result;
}


Vector* regex_search(Regex* regex, const char* string)
{
  Vector* result = NULL;

  //printf("Matching %s against %s\n", regex->pattern, string);
  if (regex->compiled2 == NULL)
    regex_compile2(regex);

  {
    int        n_matches = regex->compiled2->re_nsub + 1;
    regmatch_t matches[n_matches];

    /* Match against the regular expression. */
    if (regexec(regex->compiled2, string, n_matches, matches, 0) != 0)
      return NULL;

    /* Copy extracted subexpressions into a vector. */
    result = match2vector(string, matches, n_matches);
  }

  return result;
}


short int regex_sub(Regex*      regex,
                    const char* input,
                    char**      dest,
                    ReSubFunc   func,
                    void*       data)
{
  GList*      results   = NULL;
  GList*      item      = NULL;
  char*       match     = NULL;
  const char* sub       = NULL;
  char*       result    = NULL;
  char*       ptr       = NULL;
  int         len       = 0;
  size_t      size      = 0;

  //printf("Matching '%s' against '%s'\n", regex->pattern, input);
  if (regex->compiled2 == NULL)
    regex_compile2(regex);
  
  {
    int        n_matches = regex->compiled2->re_nsub + 1;
    regmatch_t matches[n_matches];

    /* Match against the regular expression. */
    if (regexec(regex->compiled2, input, n_matches, matches, 0) != 0) {
      *dest = strdup(input);
      return 0;
    }

    results = NULL;
    do {
      /* Extract the part *before* the current subexpression. */
      if (matches[0].rm_so > 0) {
        sub     = strndup(input, matches[0].rm_so);
        results = g_list_append(results, (void*)sub);
        size   += matches[0].rm_so;
      }
      //printf("Before value %i: %s\n", g_list_length(results), sub);

      /* Extract the subexpression and call the given function. */
      len   = matches[0].rm_eo - matches[0].rm_so;
      match = strndup(input + matches[0].rm_so, len);
      sub   = func(regex, match, data);
      if (sub == NULL) {
        item = results;
        while (item) {
          match = (char*)item->data;
          item  = item->next;
          free(match);
        }
        g_list_free(results);
        return 0;
      }
      results = g_list_append(results, (void*)strdup(sub));
      size   += strlen(sub);
      free(match);

      /* Point source to the end of the subexpression. */
      input += matches[0].rm_eo;
    } while(regexec(regex->compiled2, input, n_matches, matches, 0) == 0);

    /* Extract the part that is appended after the last subexpression. */
    results = g_list_append(results, (void*)strdup(input));
    size   += strlen(input);

    /* Copy the result into a string. */
    ptr  = result = (char*)calloc(size + 1, 1);
    item = results;
    while (item) {
      match = (char*)item->data;
      strcpy(ptr, match);
      ptr  += strlen(match);
      item  = item->next;
      free(match);
    }
    *ptr = '\0';
    g_list_free(results);
  }

  *dest = result;
  return 1;
}
